/**
Copyright 2011 Steven Moxley

This file is part of Joggle.

Joggle is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Joggle is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Joggle.  If not, see <http://www.gnu.org/licenses/>.
 */
package edu.gwu.cs.joggle;

import com.almworks.sqlite4java.*;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileWriter;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Random;
import java.security.KeyFactory;
import java.security.KeyPairGenerator;
import java.security.KeyPair;
import java.security.KeyStore;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLSocket;
import javax.swing.Timer;

/**
 * @author      Steven Moxley <steven.moxley@gmail.com>
 * @version     1
 * @since       2011-12-20
 */
public class Server
{
	// db vars
	protected final String PLAYERS = "Players";
	protected final String CLIENTS = "ActiveClients";
	protected final String AUDITORS = "Auditors";
	protected final String ADMINS = "Administrators";
	protected final String ACTIVE_GAMES = "ActiveGames";
	protected final String HISTORIC_GAMES = "HistoricGames";
	protected final Random rng = new Random();

	// needed to create Server
	private final String dictFolderPath;
	protected int maxClients;
	protected ArrayList<Player> players;
	protected ArrayList<Client> authedClients;
	protected ArrayList<Auditor> auditors;
	protected ArrayList<Administrator> admins;
	protected ArrayList<Game> activeGames;
	protected ArrayList<Dictionary> availableDictionaries;
	protected Administrator admin;
	protected SQLiteConnection dbConnection;
	protected SQLiteQueue dbQueue;
	protected int boardUnlock = 500;
  protected String currentlyCollecting;
	
	// crypto vars
	protected KeyFactory keyFactory;
	protected KeyPairGenerator rsaKeyPairGen;
	protected KeyPair rsaKeyPair;
	protected KeyStore keyStore;
	protected MessageDigest sha;

	/**
	 * Get a list of Dictionaries available for Games on this Server
	 */
	protected ArrayList<Dictionary> getAvailableDictionaries() {
		return availableDictionaries;
	}

	protected String getDictFolderPath() {
		return dictFolderPath;
	}

	/**
	 * Get a User's password hash if authorized.  Specific conditions checked: Is the requester the owner of the password?  Is the requester an authorized Auditor?  Is the requester an Administrator? 
	 * @param requested: The User whose password has was requested by the requester
	 * @param requestor: The User requesting the requested User's password
	 */
	private String getUserPassword(User requested, User requester) {
		boolean authorizedAuditor = false;
		String table = "";
		if(requester instanceof Auditor) {
			Auditor a = (Auditor) requester;
			authorizedAuditor = a.getAuthorizedPlayers().contains(requested.getUserName());
		}
		if(requester.equals(admin) || requester.equals(requested) || authorizedAuditor) {
			if(requested instanceof Administrator) { table = ADMINS; }
			else if(requested instanceof Auditor) { table = AUDITORS; }
			else { table = PLAYERS; }
			ArrayList<String> result = getCellValues(table, new String[] {"password"}, new String[] {"userName"}, new String[] {requested.getUserName()});
			return result.get(0);
		} else {
			return "You are not authorized to get this user's password.";
		}
	}
	
	/**
	 * Set a User's password hash if authorized.  Specific conditions checked: Is the requester the owner of the password?  Is the requester an authorized Auditor?  Is the requester an Administrator? 
	 * @param requested: The User whose password has was requested by the requester
	 * @param requestor: The User requesting the requested User's password
	 * @param hashedPwd: The SHA-512 hash of the User's plaintext password
	 */	
	protected void setUserPassword(User requested, User requester, String hashedPwd) {
	    writeUserLogEntry("changed password of " + requested.getUserName() + "\n.", requester);
		String table = "";
		if(requester.getUserName().equals(admin.getUserName()) || requester.equals(requested)) {
			if(requested instanceof Player) { table = PLAYERS; }
			else if(requested instanceof Auditor) { table = AUDITORS; }
			try {
				setCellValues(table, new String[] {"password"}, new String[] {hashedPwd}, new String[] {"userName"}, new String[] {requested.getUserName()});
			} catch (SQLiteException se) {
				System.out.println("Unable to set " + requested.getUserName() + "'s password.");
				se.printStackTrace();
			}
		} else {
			System.out.println("You are not authorized to set this user's password.");
		}
	}
	
	/**
	 * @param p The Player to add
	 * @param a The Administrator performing this action
	 * Register a Player's account in the Server's database
	 * @throws SQLiteException 
	 * @throws IOException 
	 */
	protected void addPlayer(Player p) {
		// add Player to appropriate DB table
		String[] playerFields = {"userName", "password", "publicKey", "firstName", "lastName", "email", "points", "gamesPlayed", "friends", "privacy", "inGame"};
		String[] playerValues = {p.getUserName(), p.getPassword(), p.getPublicKey().toString(), p.getFirstName(), p.getLastName(), p.getEmail(), "0", "", "", p.getPrivacy().toString(), "false"};
		insertRow(PLAYERS, playerFields, playerValues);
		players.add(p);
	}

	/**
	 * @param a The Player to de-authorize
	 * @param a The Administrator performing this action
	 * De-authorizes Player p by removing it from this Server's database
	 * @throws SQLiteException 
	 */
	protected void removePlayer(Player p, Administrator a) throws SQLiteException {
	    writeUserLogEntry("removed Player " + p.getUserName() + "\n.", a);
		if(a.equals(admin)) {
			// remove Player from db
			deleteRow(PLAYERS, new String[] {"userName"}, new String[] {p.getUserName()});
			players.remove(p);
		} else {
			System.out.println("You are not authorized to remove Player's on this Server.");
		}
	}

	/**
	 * @return boolean Returns true if the Player has joined a game (and is either playing it or waiting maxWait for it to begin)
	 * @param p The Player to query
	 */	
	protected Boolean isPlayerInGame(Player p) {
		//return new Boolean(getCellValues(PLAYERS, new String[] {"inGame"}, new String[] {"userName"}, new String[] {p.getUserName()}).get(0));
		// bad hacked fix
		return false;
	}
	
	/**
	 * Update a Player's in-game status.  Should be true when p is either playing a Game  or waiting maxWait for one to begin
	 * @param Player p The Player whose status to set
	 * @param inGame True if the player is either playing a Game  or waiting maxWait for one to begin
	 * @return boolean
	 */		
	protected Boolean setPlayerInGame(Player p, Boolean inGame) {
		try {
			setCellValues(PLAYERS, new String[] {"inGame"}, new String[] {inGame.toString()}, new String[] {"userName"}, new String[] {p.getUserName()});
		} catch (SQLiteException e) {
			e.printStackTrace();
		}
		return isPlayerInGame(p);
	}
	
	/**
	 * Update a Player's in-game status.  Should be true when p is either playing a Game  or waiting maxWait for one to begin
	 * @param Player p The Player whose status to set
	 * @return Integer points
	 */	
	protected Integer getPlayerPoints(Player p) {
		String points = getCellValues(PLAYERS, new String[] {"points"}, new String[] {"userName"}, new String[] {p.getUserName()}).get(0);
		if(p.equals("")) {
			return 0;
		} else {
			return new Integer(points);
		}
	}

	/**
	 * Update a Player's points
	 * @param Player p The Player whose points to set
	 * @param Integer points The number of points awarded according to the rules of Boggle from all games
	 * @return Integer player's points
	 */	
	protected Integer setPlayerPoints(Player p, Integer points) {
	    writeUserLogEntry("points set to " + points + ".\n", p);
		try {
			setCellValues(PLAYERS, new String[] {"points"}, new String[] {points.toString()}, new String[] {"userName"}, new String[] {p.getUserName()});
		} catch (SQLiteException se) {
			System.out.println("Unable to set " + p.getUserName() + "'s points.");
			se.printStackTrace();
		}
		return getPlayerPoints(p);
	}
	
	/**
	 * Update a Player's in-game status.  Should be true when p is either playing a Game or waiting maxWait for one to begin
	 * @param String uName The username of the Player
	 * @return String a string representation of games played
	 */	
	protected String getGamesPlayed(String uName) {
		String games;
		try {
			games = getCellValues(PLAYERS, new String[] {"gamesPlayed"}, new String[] {"userName"}, new String[] {uName}).get(0);
		} catch (IndexOutOfBoundsException ioob) {
			games = "";
		}
			return games;
	}
	
	/**
	 * Update a Player's number of played Games
	 * @param String uName The username of the Player whose number of Games to set
	 * @param Integer gamesPlayed The number of Games played by the given Player
	 * @return String
	 */	
	protected String setGamesPlayed(String uName, Integer gamesPlayed) {
		String result = "";
		try {
			setCellValues(PLAYERS, new String[] {"gamesPlayed"}, new String[] {gamesPlayed.toString()}, new String[] {"userName"}, new String[] {uName});
			result = getGamesPlayed(uName);
		} catch (SQLiteException se) {
			result = "Unable to get games played by " + uName + ".";
			se.printStackTrace();
		}
		return result;
	}
	
	/**
	 * Update a Player's in-game status.  Should be true when p is either playing a Game  or waiting maxWait for one to begin
	 * @param Player p The Player whose status to set
	 * @param inGame True if the player is either playing a Game  or waiting maxWait for one to begin
	 * @return ArrayList<String> of friend names
	 */	
	protected ArrayList<String> getPlayerFriends(Player p) {
		ArrayList<String> friends = getCellValues(PLAYERS, new String[] {"friends"}, new String[] {"userName"}, new String[] {p.getUserName()});
		String[] userNames;
		ArrayList<String> friendNames = new ArrayList<String>();
		for(int i = 0; i < friends.size(); i++) {
			userNames = friends.get(i).split("#");
			for(String s : userNames) {
				if(!friendNames.contains(s)) {
					friendNames.add(s);
				}
			}
		}
		return friendNames;
	}
	
	/**
	 * Get a Player's privacy setting.  Administrators and authorized Auditors will always be able to view a Player's profile, regardless of their privacy setting.  Otherwise, 0 means the profile is publicly visible, 1 limits the profile's visibility to friends (rather than all Players), and 2 means protected even from friends.
	 * @param String uName The username of the Player whose privacy setting to get
	 * @return Integer representing player's privacy
	 */	
	protected Integer getPlayerPrivacy(String uName) {
		return new Integer(getCellValues(PLAYERS, new String[] {"privacy"}, new String[] {"userName"}, new String[] {uName}).get(0));
	}
	
    /**
     * @param name String representation of player's name
     * @param String representation of player's privacy settings
     */
	protected String getPlayerPrivacyAsString(String name) {
		int p = getPlayerPrivacy(name);
		if(p == 0) {
			return "Public";
		} else if(p == 1) {
			return "Friendly";
		} else {
			return "Private";
		}
	}
	
	/**
	 * Set a Player's privacy setting.  Administrators and authorized Auditors will always be able to view a Player's profile, regardless of their privacy setting.  Otherwise, 0 means the profile is publicly visible, 1 limits the profile's visibility to friends (rather than all Players), and 2 means protected even from friends.
	 * @param String uName The username of the Player whose privacy setting to set
	 * @param Integer privacy 0 means the profile is publicly visible, 1 limits the profile's visibility to friends (rather than all Players), and 2 means protected even from friends
	 * @return Integer
	 */
	protected Integer setPlayerPrivacy(String uName, Integer privacy) {
		if(privacy == 0 || privacy == 1 || privacy == 2) {
			try {
				setCellValues(PLAYERS, new String[] {"privacy"}, new String[] {privacy.toString()}, new String[] {"userName"}, new String[] {uName});
			} catch (SQLiteException se) {
				System.out.println("Unable to set " + uName + "'s privacy.");
				se.printStackTrace();
			}
		}
		else {
			System.out.println("Invalid privacy setting!");
		}
		return getPlayerPrivacy(uName);
	}
	
	/**
	 * Re-create a Player from the database row with matching username
	 * @param String uName The username of the Player to get
	 * @return Player
	 */
	private Player getPlayerFromName(String userName) {
		ArrayList<String> player = getCellValues(PLAYERS, new String[] {"*"}, new String[] {"userName"}, new String[] {userName});
		
		try {
			// "userName", "password", "publicKey", "firstName", "lastName", "email", "points", "gamesPlayed", "friends", "privacy", "inGame"
			// String fName, String lName, String uName, String email, String pwd, Integer p, Server s, Boolean newAcct
			return new Player(player.get(3), player.get(4), player.get(0), player.get(5), player.get(1), new Integer(player.get(6)), this, false);
		} catch (NumberFormatException nfe) {
			System.out.println("There was an error reading the Player's privacy settings!  Defaulting to privacy enabled.");
			nfe.printStackTrace();
			return new Player(player.get(0), player.get(2), player.get(3), player.get(4), player.get(1), 1, this, false);
		}
	}
	
	/**
	 * Re-create an Auditor from the database row with matching username
	 * @param String uName The username of the Auditor to get
	 * @return Auditor
	 */
	private Auditor getAuditorFromName(String userName) {
		ArrayList<String> auditor = getCellValues(AUDITORS, new String[] {"*"}, new String[] {"userName"}, new String[] {userName});
		
		try {
			// "userName", "password", "publicKey", "firstName", "lastName", "email", "authorizedGames", "authorizedPlayers"
			// String fName, String lName, String uName, String email, String pwd, ArrayList<Game> games, ArrayList<Player> players, Server s, Administrator a

			// reconstruct list of Games this Auditor can audit
			String[] games = auditor.get(6).split("#");
			ArrayList<Game> playedGames = getHistoricGames();
			ArrayList<Game> authedGames = new ArrayList<Game>(games.length);
			for(Game g : playedGames) { 
				for(String s : games) {
					if(g.getSeed().toString().equals(s)) {
						authedGames.add(g);
					}
				}
			}
			
			// reconstruct list of Players this Auditor can audit
			String[] players = auditor.get(7).split("#");
			ArrayList<Player> authedPlayers = new ArrayList<Player>(players.length);
			for(String p : players) { authedPlayers.add(getPlayerFromName(p)); }
			
			return new Auditor(auditor.get(3), auditor.get(4), auditor.get(0), auditor.get(5), auditor.get(1), authedGames, authedPlayers, this, admin, false);
		} catch (Exception e) {
			return new Auditor(auditor.get(3), auditor.get(4), auditor.get(0), auditor.get(5), auditor.get(1), new ArrayList<Game>(), new ArrayList<Player>(), this, admin, false);
		}
	}
	
	/**
	 * Re-create an Administrator from the database row with matching username
	 * @param String uName The username of the Administrator to get
	 * @return Administrator
	 */
	private Administrator getAdminFromName(String userName) {
		ArrayList<String> admin = getCellValues(ADMINS, new String[] {"*"}, new String[] {"userName"}, new String[] {userName});
		// "userName", "password", "publicKey", "firstName", "lastName", "email"
		// String fName, String lName, String uName, String email, String pwd
		return new Administrator(admin.get(3), admin.get(4), admin.get(0), admin.get(5), admin.get(1));
	}
	
	/**
	 * Make two Players friends by adding them to each other's friend list in the database
	 * @param p1 The Player's to add to each other's friend list
	 * @param p2 The Player's to add to each other's friend list
	 * @throws SQLiteException
	 */
	protected void makeFriends(Player p1, Player p2) throws SQLiteException {
	    writeUserLogEntry("added Player " + p2.getUserName() + " to his/her friends list.\n", p1);
		// add p2 to p1's list of friends
		String friendsList1 = p1.getFriendsAsString()+p2.getUserName()+"#";
		//System.out.println(friendsList1);
		setCellValues(PLAYERS, new String[] {"friends"}, new String[] {friendsList1}, new String[] {"userName"}, new String[] {p1.getUserName()});
		// add p1 to p2's list of friends
		String friendsList2 = p2.getFriendsAsString()+p1.getUserName()+"#";
		//System.out.println(friendsList2);
		setCellValues(PLAYERS, new String[] {"friends"}, new String[] {friendsList2}, new String[] {"userName"}, new String[] {p2.getUserName()});
		// force Player's to update their friends' lists 
		p1.getFriends();
		p2.getFriends();
	}
	
	/**
	 * Lets the Server know the given Client is online and adds the Client to the database
	 * @param c The Client to add
	 * @throws SQLiteException 
	 */
	protected void addClient(Client c) throws SQLiteException {
		if(authedClients.size() < maxClients) {
			// add Client to appropriate DB table
			insertRow(CLIENTS, new String[] {"id", "lastLogin"}, new String[] {c.getID().toString(), "datetime('now')"});
			authedClients.add(c);
			//sendClientMessage("Hello, client #" + c.getID() + "!", c);
		} else {
			System.out.println("Sorry, the server is too busy to connect you.  Try again later.");
		}
	}

	/**
	 * De-authorizes Client c by removing it from this Server's database
	 * @param c The Client to de-authorize
	 */
	protected void removeClient(Client c) {
		// remove Client from db
		deleteRowID(CLIENTS, c.getID());
		authedClients.remove(c);
	}

    /**
     * @param a Auditor
     * @param ad Administrator
     * @throws SQLiteException
     */
	protected void addAuditor(Auditor a, Administrator ad) throws SQLiteException {
	    writeUserLogEntry("added Auditor " + a.getUserName() + ".\n", ad);
		if(admin.equals(ad)) {
			// add Auditor to appropriate DB table
			String[] auditorFields = {"userName", "password", "publicKey", "firstName", "lastName", "email", "authorizedGames", "authorizedPlayers"};
			String[] auditorValues = {a.getUserName(), a.getPassword(), a.getPublicKey().toString(), a.getFirstName(), a.getLastName(), a.getEmail(), a.getAuthorizedGamesAsString(), a.getAuthorizedPlayersAsString()};
			insertRow(AUDITORS, auditorFields, auditorValues);
			auditors.add(a);
		} else {
			System.out.println("You are not authorized to add Auditors on this Server.");
		}
	}

	/**
	 * De-authorizes Auditor a by removing it from this Server's database
	 * @param a The Auditor to de-authorize
	 * @param ad Administrator
	 * @throws SQLiteException 
	 */
	protected void removeAuditor(Auditor a, Administrator ad) throws SQLiteException {
	    writeUserLogEntry("removed Auditor " + a.getUserName() + ".\n", ad);
		if(admin.equals(ad)) {
			// remove Auditor from db
			deleteRow(AUDITORS, new String[] {"userName"}, new String[] {a.getUserName()});
			auditors.remove(a);
		} else {
			System.out.println("You are not authorized to remove Player's on this Server.");
		}
	}

    /**
     * @param a Auditor
     * @return ArrayList<String> representation of the games this auditor is authorized to audit
     */
	protected ArrayList<String> getAuditorsAuthorizedGames(Auditor a) {
		ArrayList<String> games = getCellValues(AUDITORS, new String[] {"authorizedGames"}, new String[] {"userName"}, new String[] {a.getUserName()});
		String[] gameSeeds;
		ArrayList<String> authorizedGames = new ArrayList<String>();
		for(int i = 0; i < games.size(); i++) {
			gameSeeds = games.get(i).split("#");
			for(String s : gameSeeds) {
				if(!authorizedGames.contains(s)) {
					authorizedGames.add(s);
				}
			}
		}
		return authorizedGames;
	}
	
    /**
     * @param a Auditor
     * @return ArrayList<String> representation of players this auditor is authorized to audit
     */
	protected ArrayList<String> getAuditorsAuthorizedPlayers(Auditor a) {
		ArrayList<String> players = getCellValues(AUDITORS, new String[] {"authorizedPlayers"}, new String[] {"userName"}, new String[] {a.getUserName()});
		String[] userNames;
		ArrayList<String> authorizedPlayers = new ArrayList<String>();
		for(int i = 0; i < players.size(); i++) {
			userNames = players.get(i).split("#");
			for(String s : userNames) {
				if(!authorizedPlayers.contains(s)) {
					authorizedPlayers.add(s);
				}
			}
		}
		return authorizedPlayers;	
	}
	
 	/**
	 * Update a an Auditor's list of authorized Games
	 * @param Auditor a The Auditor whose list to update
	 */	
	protected ArrayList<String> setAuditorGames(Auditor a) {
		ArrayList<String> result = new ArrayList<String>();
		try {
			//String tableName, String[] uFields, String[] uValues, String[] wFields, String[] wValues
			setCellValues(AUDITORS, new String[] {"authorizedGames"}, new String[] {a.getAuthorizedGamesAsString()}, new String[] {"userName"}, new String[] {a.getUserName()});
			result = getAuditorsAuthorizedGames(a);
		} catch (SQLiteException se) {
			System.out.println("Unable to set authorized games for " + a + ".");
			//se.printStackTrace();
		}
		return result;
	}
	
	/**
	 * Update a an Auditor's list of authorized Players
	 * @param Auditor a The Auditor whose list to update
	 */	
	protected ArrayList<String> setAuditorPlayers(Auditor a) {
		ArrayList<String>  result = new ArrayList<String>();
		try {
			setCellValues(AUDITORS, new String[] {"authorizedPlayers"}, new String[] {a.getAuthorizedPlayersAsString()}, new String[] {"userName"}, new String[] {a.getUserName()});
			result = getAuditorsAuthorizedPlayers(a);
		} catch (SQLiteException se) {
			System.out.println("Unable to set authorized games for " + a + ".");
			//se.printStackTrace();
		}
		return result;
	}
   
    /**
     * @param a Administrator
     * @throws SQLiteException
     */
	protected void addAdmin(Administrator a) throws SQLiteException {
		// add Auditor to appropriate DB table
		String[] adminFields = {"userName", "password", "publicKey", "firstName", "lastName", "email"};
		String[] adminValues = {a.getUserName(), a.getPassword(), a.getPublicKey().toString(), a.getFirstName(), a.getLastName(), a.getEmail()};
		insertRow(ADMINS, adminFields, adminValues);
		admins.add(a);
	}

	/**
	 * De-authorizes Auditor a by removing it from this Server's ACL
	 * @param a The Auditor to de-authorize
	 * @throws SQLiteException 
	 */
	protected void removeAuditor(Administrator a) throws SQLiteException {
		if(admins.size() > 1) {
			// remove Auditor from db
			deleteRow(ADMINS, new String[] {"userName"}, new String[] {a.getUserName()});
			auditors.remove(a);
		} else {
			System.out.println("The Server must have at least one Administrator.");
		}
	}
	
	/**
	 * Returns a User re-created from entries in the database row with the matching username
	 * @param uName  The username of the desired User
	 * @param type The class of the desired User: Player, Administrator, or Auditor
	 * @param password String
	 * @return User
	 */
	protected User getUserFromName(String uName, String type, String password) {
		// get the user
		User u;
		if(type.equals("Administrator")) {
			u = getAdminFromName(uName);
		} else if(type.equals("Auditor")) {
			u = getAuditorFromName(uName);
		} else {
			u = getPlayerFromName(uName);
		}
		
		// verify password
		String pwd = getUserPassword(u, admin);
		if(pwd.length() == 0 || pwd.equals(hash(""))) { 
			System.out.println("User " + uName +" has been banned!"); 
			return null;
		}
		else if(!hash(password).equals(pwd)) {
			System.out.println("#### WRONG PASSWORD ####");
			return null;
		} else {
			return u;
		}
	}
	
	/**
	 * Returns a User re-created from entries
	 * @param requested String
	 * @param type String
	 * @param requester User
	 * @return User
	 */
	protected User getUserFromName(String requested, String type, User requester) {
		writeUserLogEntry("asked for user: " + requested + " of type: " + type + ".\n", requester);
		User u;
		boolean authorizedAuditor = false;
		if(type.equals("Auditor")) {
			Auditor a = (Auditor) requester;
			authorizedAuditor = a.getAuthorizedPlayers().contains(requested);
		}
		if(requester.getUserName().equals(admin.getUserName()) || requester.getUserName().equals(requested) || authorizedAuditor) {
			if(type.equals("Administrator")) { u = getAdminFromName(requested); }
			else if(type.equals("Auditor")) { u = getAuditorFromName(requested); }
			else { u = getPlayerFromName(requested); }
		} else {
			System.out.println("You are not authorized to get this User.");
			u = null;
		}
		return u;
	}
	
	/**
	 * @return ArrayList<User> ArrayList of all Users of all types known by this Server
	 */
	protected ArrayList<User> getAllUsers() {
		ArrayList<String> playerNames = getCellValues(PLAYERS, new String[] {"*"});
		ArrayList<String> auditorNames = getCellValues(AUDITORS, new String[] {"*"});
		ArrayList<String> adminNames = getCellValues(ADMINS, new String[] {"*"});
		ArrayList<User> users = new ArrayList<User>(playerNames.size() + auditorNames.size() + adminNames.size());
		for(String n : playerNames) { users.add(getPlayerFromName(n)); }
		for(String n : auditorNames) { users.add(getAuditorFromName(n)); }
		for(String n : adminNames) { users.add(getAdminFromName(n)); }
		return users;
	}
	
    /**
     * @return String[] of player names
     */
    protected String[] getPlayerNames() {
		ArrayList<String> playerNames = getCellValues(PLAYERS, new String[] {"*"});
		String[] pNames = new String[playerNames.size()];
		for (int i = 0; i < playerNames.size(); i++) {
		    pNames[i] = playerNames.get(i);
		}
		return pNames;
    }
	
	/**
	 * Registers a new active Game on this Server for storage and advertisement to other Players
	 * @param g The Game to register
	 */
	protected void addActiveGame(Game g) {
		if(g.getSize() > 4 && g.getOwner().getPoints() < boardUnlock) {
			System.out.println(g.getOwner() + " is not authorized to create boards bigger than 4x4 because he/she has only " + g.getOwner().getPoints() + " points and " + boardUnlock + " are required.");
		} else if(isPlayerInGame(g.getOwner())) {
			System.out.println("You are already in a game!");
		} else {
			// add Game to appropriate DB table
			String[] activeGameFields = {"id", "minPlayers", "maxPlayers", "minPoints", "maxPoints", "maxWait", "duration", "seed", "size", "owner", "players", "allowedPlayers", "bannedPlayers", "dictionary"};
			String[] activeGameValues = {g.getOwner().getUserName() + "-" + g.getSeed(), g.getMinPlayers().toString(), g.getMaxPlayers().toString(), g.getMinPoints().toString(), g.getMaxPoints().toString(), g.getMaxWait().toString(), g.getDuration().toString(), g.getSeed().toString(), g.getSize().toString(), g.getOwner().getUserName(), g.getPlayersAsString(), g.getAllowedPlayersAsString(), g.getBannedPlayersAsString(), g.getDictionary().getName()};
			insertRow(ACTIVE_GAMES, activeGameFields, activeGameValues);
			activeGames.add(g);
		}
	}
	
	/**
	 * @return ArrayList<Game> an ArrayList of Games available for Players to join.  A Game remains active until the minimum number of Players have joined.
	 * @throws IOException
	 * @throws SQLiteException
	 */
	protected ArrayList<Game> getActiveGames() throws IOException, SQLiteException {
		ArrayList<String> IDs = getCellValues(ACTIVE_GAMES, new String[] {"id"});
		ArrayList<Game> games = new ArrayList<Game>(IDs.size());
		for(String i : IDs) {
			ArrayList<String> game = getCellValues(ACTIVE_GAMES, new String[] {"*"}, new String[] {"id"}, new String[] {i});
			ArrayList<String> pNames;
			try {
				String[] pl = game.get(10).split("#");  // 10 is players cell number in db
				pNames = new ArrayList<String>(pl.length);
				for(String p : pl) { pNames.add(p); }
			} catch (NullPointerException npe) {
				pNames = new ArrayList<String>();
			}
			ArrayList<String> allowedPlayers;
			try {
				String[] allowed = game.get(11).split("#");  // 11 is allowedPlayers cell number in db
				allowedPlayers = new ArrayList<String>(allowed.length);
				for(String a : allowed) { allowedPlayers.add(a); }
			} catch (NullPointerException npe) {
				allowedPlayers = new ArrayList<String>();
			}
			ArrayList<String> bannedPlayers;
			try {
				String[] banned = game.get(12).split("#"); // 12 is bannedPlayers cell number in db
				bannedPlayers = new ArrayList<String>(banned.length);
				for(String b : banned) { bannedPlayers.add(b); }
			} catch (NullPointerException npe) {
				bannedPlayers = new ArrayList<String>();
			}
			Player owner = getPlayerFromName(game.get(9));
			
			Game newGame = new Game(new Integer(game.get(1)), new Integer(game.get(2)), new Integer(game.get(3)), new Integer(game.get(4)), new Integer(game.get(5)), new Integer(game.get(6)), new Integer(game.get(7)), new Integer(game.get(8)), pNames, allowedPlayers, bannedPlayers, getAvailableDictionaries().get(0), owner, false);
			games.add(newGame);
			//System.out.println(games.get(games.size()-1));
		}
		return games;
	}
	
	/**
	 * Stores played Games in the appropriate database table
	 * @param g Game
	 * @throws SQLiteException
	 */
	protected void addHistoricGame(Game g) throws SQLiteException {
		// add Game to appropriate DB table
		String id = g.getOwner().getUserName() + "-" + g.getSeed();
		String[] historicGameFields = {"id", "minPlayers", "maxPlayers", "minPoints", "maxPoints", "maxWait", "duration", "seed", "size", "owner", "players", "allowedPlayers", "bannedPlayers", "dictionary"};
		String[] historicGameValues = {id, g.getMinPlayers().toString(), g.getMaxPlayers().toString(), g.getMinPoints().toString(), g.getMaxPoints().toString(), g.getMaxWait().toString(), g.getDuration().toString(), g.getSeed().toString(), g.getSize().toString(), g.getOwner().getUserName(), g.getPlayersAsString(), g.getAllowedPlayersAsString(), g.getBannedPlayersAsString(), g.getDictionary().getName()};
		insertRow(HISTORIC_GAMES, historicGameFields, historicGameValues);
		
		// TO DO: remove Game from Active Games table
		deleteRowID(ACTIVE_GAMES, id);
		deleteRow(ACTIVE_GAMES, new String[] {"owner"}, new String[] {g.getOwner().getUserName()});
		deleteRow(ACTIVE_GAMES, new String[] {"seed"}, new String[] {g.getSeed().toString()});
		activeGames.remove(g);
	}
	
	/**
	 * @returns ArrayList<Game> an ArrayList of played Games
	 * @throws IOException
	 * @throws SQLiteException
	 */
	protected ArrayList<Game> getHistoricGames() throws IOException, SQLiteException {
		ArrayList<String> IDs = getCellValues(HISTORIC_GAMES, new String[] {"id"});
		ArrayList<Game> games = new ArrayList<Game>(IDs.size());
		for(String i : IDs) {
			ArrayList<String> game = getCellValues(HISTORIC_GAMES, new String[] {"*"}, new String[] {"id"}, new String[] {i});
			ArrayList<String> pNames;
			try {
				String[] pl = game.get(10).split("#");  // 10 is players cell number in db
				pNames = new ArrayList<String>(pl.length);
				for(String p : pl) { pNames.add(p); }
			} catch (NullPointerException npe) {
				pNames = new ArrayList<String>();
			}
			ArrayList<String> allowedPlayers;
			try {
				String[] allowed = game.get(11).split("#");  // 11 is allowedPlayers cell number in db
				allowedPlayers = new ArrayList<String>(allowed.length);
				for(String a : allowed) { allowedPlayers.add(a); }
			} catch (NullPointerException npe) {
				allowedPlayers = new ArrayList<String>();
			}
			ArrayList<String> bannedPlayers;
			try {
				String[] banned = game.get(12).split("#"); // 12 is bannedPlayers cell number in db
				bannedPlayers = new ArrayList<String>(banned.length);
				for(String b : banned) { bannedPlayers.add(b); }
			} catch (NullPointerException npe) {
				bannedPlayers = new ArrayList<String>();
			}
			Player owner = getPlayerFromName(game.get(9));
			
			Game newGame = new Game(new Integer(game.get(1)), new Integer(game.get(2)), new Integer(game.get(3)), new Integer(game.get(4)), new Integer(game.get(5)), new Integer(game.get(6)), new Integer(game.get(7)), new Integer(game.get(8)), pNames, allowedPlayers, bannedPlayers, getAvailableDictionaries().get(0), owner, false);
			games.add(newGame);
		}
		return games;
	}
	
	/**
	 * Returns the minimum number of Players who need to join the given Game before it can be played
	 * @param g The Game to query
	 * @return Integer min players
	 */
	protected Integer getGameMinPlayers(Game g) {
		ArrayList<String> min = getCellValues(ACTIVE_GAMES, new String[] {"minPlayers"}, new String[] {"seed"}, new String[] {g.getSeed().toString()});
		//String[] m;
		ArrayList<Integer> minPlayers = new ArrayList<Integer>();
		for(String m : min) {
			System.out.println(m);
			minPlayers.add(new Integer(m));
		}
		return minPlayers.get(0);
	}

	/**
	 * Returns the maximum number of Players allowed to join the given Game before it is full
	 * @param g The Game to query
	 * @return Integer max players
	 */
	protected Integer getGameMaxPlayers(Game g) {
		return new Integer(getCellValues(ACTIVE_GAMES, new String[] {"maxPlayers"}, new String[] {"seed"}, new String[] {g.getSeed().toString()}).get(0));
	}
	
	/**
	 * Returns the minimum number of points from previous Games Players need to join the given Game
	 * @param g The Game to query
	 * @return Integer min points
	 */
	protected Integer getGameMinPoints(Game g) {
		return new Integer(getCellValues(ACTIVE_GAMES, new String[] {"minPoints"}, new String[] {"seed"}, new String[] {g.getSeed().toString()}).get(0));
	}
	
	/**
	 * Returns the maximum number of points Players may have in the given Game
	 * @param g The Game to query
	 * @return Integer max points
	 */
	protected Integer getGameMaxPoints(Game g) {
		return new Integer(getCellValues(ACTIVE_GAMES, new String[] {"maxPoints"}, new String[] {"seed"}, new String[] {g.getSeed().toString()}).get(0));
	}
	
	/**
	 * Returns the number of seconds to wait after minPlayers have joined the given Game before starting to play
	 * @param g The Game to query
	 * @return Integer max wait
	 */
	protected Integer getGameMaxWait(Game g) {
		return new Integer(getCellValues(ACTIVE_GAMES, new String[] {"maxWait"}, new String[] {"seed"}, new String[] {g.getSeed().toString()}).get(0));
	}
	
	/**
	 * Returns the number of seconds this Game will last
	 * @param g The Game to query
	 * @return Integer game duration
	 */
	protected Integer getGameDuration(Game g) {
		Integer d = new Integer(getCellValues(ACTIVE_GAMES, new String[] {"duration"}, new String[] {"seed"}, new String[] {g.getSeed().toString()}).get(0));
		System.out.println(d);
		return d;
	}
	
	/**
	 * Returns the dimensions of the Game Board
	 * @param g The Game to query
	 * @return Integer game size
	 */
	protected Integer getGameSize(Game g) {
		return new Integer(getCellValues(ACTIVE_GAMES, new String[] {"size"}, new String[] {"seed"}, new String[] {g.getSeed().toString()}).get(0));
	}
	
	/**
	 * Returns the object corresponding to the Game hosted/created by the given User
	 * @param ownerName The username of the User who created a Game
	 * @return Game
	 */
	protected Game getActiveGameFromOwner(String ownerName) {
		//int seed = new Integer(getCellValues(ACTIVE_GAMES, new String[] {"seed"}, new String[] {"owner"}, new String[] {ownerName}).get(0));
		//String id = ownerName + "-" + seed;
		ArrayList<String> g = getCellValues(ACTIVE_GAMES, new String[] {"*"}, new String[] {"owner"}, new String[] {ownerName});
		if(g.size() > 0) {
			ArrayList<String> p;
			//"id", "minPlayers", "maxPlayers", "minPoints", "maxPoints", "maxWait", "duration", "seed", "size", "owner", "players", "allowedPlayers", "bannedPlayers", "dictionary"
			//Integer minPlay, Integer maxPlay, Integer minPoint, Integer maxPoint, Integer maxW, Integer du, Integer sd, Integer sz, ArrayList<String> p, ArrayList<String> allowed, ArrayList<String> banned, Dictionary di, Player o, Boolean newG
			try {
				String[] pl = g.get(10).split("#");  // 10 is players cell number in db
				p = new ArrayList<String>(pl.length);
				for(String a : pl) { p.add(a); }
			} catch (NullPointerException npe) {
				p = new ArrayList<String>();
			}
			ArrayList<String> allowedPlayers;
			try {
				String[] allowed = g.get(11).split("#");  // 11 is allowedPlayers cell number in db
				allowedPlayers = new ArrayList<String>(allowed.length);
				for(String a : allowed) { allowedPlayers.add(a); }
			} catch (NullPointerException npe) {
				allowedPlayers = new ArrayList<String>();
			}
			ArrayList<String> bannedPlayers;
			try {
				String[] banned = g.get(12).split("#"); // 12 is bannedPlayers cell number in db
				bannedPlayers = new ArrayList<String>(banned.length);
				for(String b : banned) { bannedPlayers.add(b); }
			} catch (NullPointerException npe) {
				bannedPlayers = new ArrayList<String>();
			}
			Player owner = getPlayerFromName(g.get(9));
			
			return new Game(new Integer(g.get(1)), new Integer(g.get(2)), new Integer(g.get(3)), new Integer(g.get(4)), new Integer(g.get(5)), new Integer(g.get(6)), new Integer(g.get(7)), new Integer(g.get(8)), p, allowedPlayers, bannedPlayers, getAvailableDictionaries().get(0), owner, false);		
		} else {
			// potentially unsafe since a malicous user could invoke this method to make it look like someone else (ownerName) was hosting the game
			return new Game(getPlayerFromName(ownerName));
		}
	}
	
	/**
	 * Adds the given Player to the given Game if the Player has not been banned and the Game is not full.  Returns the updated list of joined Players.  Starts a timer for maxWait seconds if minPlayers have joined.
	 * @param p The Player who wishes to join the given Game
	 * @param g The Game to join
	 * @return ArrayList<String>
	 */
	protected ArrayList<String> addGamePlayer(Player p, final Game g) {
		ArrayList<String> gamePlayers = getGamePlayers(g);
		if(p.isInGame()) { System.out.println("You are already in a Game!"); }
		else if(!g.getBannedPlayers().contains(p)) {	
			try {
				gamePlayers.add(p.getUserName());
				g.setPlayers(gamePlayers);
				setCellValues(ACTIVE_GAMES, new String[] {"players"}, new String[] {g.getPlayersAsString()}, new String[] {"owner"}, new String[] {g.getOwner().getUserName()});
				setPlayerInGame(p, true);
			} catch (SQLiteException se) {
				System.out.println("Unable to add " + p.getUserName() + " to game with seed " + g.getSeed() + ".");
				se.printStackTrace();
			}
		} else {
			System.out.println("Sorry, you have been banned from this Game.");
		}
		
		// if the minimum number of players have joined the game, wait maxWait seconds for others to join before starting the game
		// checks to make sure the wait timer hasn't started to avoid restarting the timer or having multiple timers for the same Game
		if(g.getPlayers().size() >= g.getMinPlayers() && !g.isWaiting()) {
			g.setWaiting(true);
			Timer waitTimer = new Timer(g.getMaxWait(), new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					// make sure no one quit the Game after the countdown started
					if(g.getPlayers().size() >= g.getMinPlayers()) {
						startGameTimer(g);
					}
					g.setWaiting(false);
				}
			    });
			waitTimer.setRepeats(false);
			waitTimer.start();
		}
		
		return g.getPlayers();
	}
	
	/**
	 * Removes the given Player from the given Game.  Returns the updated list of joined Players.
	 * @param p The Player who wishes to leave the given Game
	 * @param g The Game to leave
	 * @param ArrayList<String>
	 */
	protected ArrayList<String> removeGamePlayer(Player p, Game g) {
		ArrayList<String> gamePlayers = getGamePlayers(g);
		gamePlayers.remove(p.getUserName());
		g.setPlayers(gamePlayers);
		
		try {
			setCellValues(ACTIVE_GAMES, new String[] {"players"}, new String[] {g.getPlayersAsString()}, new String[] {"seed"}, new String[] {g.getSeed().toString()});
		} catch (SQLiteException se) {
			System.out.println("Unable to add " + p.getUserName() + " to game with seed " + g.getSeed() + ".");
			se.printStackTrace();
		}
		return g.getPlayers();
	}
	
	/**
	 * Returns an ArrayList of Players who have joined the given Game
	 * @param g The Game to query
	 * @return ArrayList<String>
	 */
	protected ArrayList<String> getGamePlayers(Game g) {		
		ArrayList<String> players = getCellValues(ACTIVE_GAMES, new String[] {"players"}, new String[] {"owner"}, new String[] {g.getOwner().getUserName()});
		String[] userNames;
		ArrayList<String> playerNames = new ArrayList<String>();
		if(players.size() > 0) {
			for(int i = 0; i < players.size(); i++) {
				userNames = players.get(i).split("#");
				for(String s : userNames) {
					if(!playerNames.contains(s)) {
						playerNames.add(s);
					}
				}
			}
		} else {
			playerNames = new ArrayList<String>();
		}
		return playerNames;
	}
	
	/**
	 * Returns an String of Players who were put on the given Game's whitelist
	 * @param g The Game to query
	 * @return String
	 */
	protected String getGameAllowedPlayers(Game g) {
		return new String(getCellValues(ACTIVE_GAMES, new String[] {"allowedPlayers"}, new String[] {"seed"}, new String[] {g.getSeed().toString()}).get(0));
	}
	
	/**
	 * Returns an String of Players who were put on the given Game's blacklist
	 * @param g The Game to query
	 * @return String
	 */
	protected String getGameBannedPlayers(Game g) {
		//return new String(getCellValues(ACTIVE_GAMES, new String[] {"bannedPlayers"}, new String[] {"seed"}, new String[] {g.getSeed().toString()}).get(0));
		ArrayList<String> results = getCellValues(ACTIVE_GAMES, new String[] {"bannedPlayers"}, new String[] {"seed"}, new String[] {g.getSeed().toString()});
		if(results.size() > 0) {
			return results.get(0);
		} else {
			return "";
		}
	}
	
	/**
	 * Returns the Dictionary used for the given Game
	 * @param g The Game to query
	 * @return String name of the dictionary used
	 */
	protected String getGameDictionary(Game g) {
		return new String(getCellValues(ACTIVE_GAMES, new String[] {"dictionary"}, new String[] {"seed"}, new String[] {g.getSeed().toString()}).get(0));
	}
   
  /**
	* Returns the number of points required to unlokc 5x5 Boards on this Server
	*/
	protected int getBoardUnlock() {
		return boardUnlock;
	}
	
	/**
	 * Starts a timer for duration seconds.  When the Game ends the Game is moved to a database table for played Games, each Player's in game status is updated, and each Auditor will audit the oldest Game and Player in their queues.
	 * @param g Game
	 */
	protected void startGameTimer(final Game g) {
		//System.out.println("There are " + g.getPlayers().size() + " players in this Game: " + g.getPlayersAsString());
		System.out.println("Starting Game! " + g.getDuration() + " seconds remaining...");
		g.initBoard();
		Timer gameTimer = new Timer(g.getDuration(), new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				try {
					currentlyCollecting = g.getOwner() + "-" + g.getSeed();
					g.collectWordLists(admin.getUserName());
					
					// end the Game and validate it
					addHistoricGame(g);
					activeGames.remove(g);
					System.out.println(validateGame(g));
					
					// update each Player's in game status
					for(String p : g.getPlayers()) {
						setPlayerInGame(getPlayerFromName(p), false);
					}
          currentlyCollecting = "";
					
					// make Auditors review one Game and one Player if there's anything in their queue
					for(Auditor a : auditors) {
						if(a.authorizedGames.size() > 0) {
							try {
								System.out.println(a.getUserName() + " is auditing a Game seeded with " + g.getSeed() + "...");
								// couldn't respond to the questions the auditor is asked in reviewGame() when running in terminal; waiting for line keep GUI from getting any input
								a.reviewGame(g);
							} catch (Exception e1) {
								e1.printStackTrace();
							}
						}
						if(a.authorizedPlayers.size() > 0) {
							try {
								Player p = a.getAuthorizedPlayers().get(0);
								System.out.println(a.getUserName() + " is auditing Player " + p + "...");
								// couldn't respond to the questions the auditor is asked in reviewGame() when running in terminal; waiting for line keep GUI from getting any input
								a.reviewPlayer(p);
							} catch (Exception e2) {
								e2.printStackTrace();
							}
						}
					}
					
				} catch (SQLiteException e1) {
					e1.printStackTrace();
				}
			}
		    });
		gameTimer.setRepeats(false);
		gameTimer.start();
	}
   
   protected Boolean verifyWordListCollect(Game g) {
		if(currentlyCollecting.equals(g.getOwner() + "-" + g.getSeed())) {
			return true;
		} else {
			return false;
		}
	}
	
	/**
	 * Returns a String explaining what happened during the given Game.  Players are awarded points according to the rules of Boggle for each valid word they submitted.  A log file is created with these details for later use by an Auditor.
	 * Have the server validate the results of a game. Validation includes checking if there were players that did not submit a word list, if any player complained about the game, if any player submitted a statistically unlikely number of words (currently a hardcoded upper limit), or if any word list contained words it should not have. This function calculates the point rewards for involved players and generates the log file associated with the game. In the event any of the above conditions are met, the log file is assigned to an auditor for review. If none of these conditions occur then the game is considered properly validated, and the players' point totals are updated
	 * @param g The Game to validate
	 * @retun String
	 */
	protected String validateGame(Game g) {
		String log = "";
		String logFile = "/Users/steve/workspace/gwu-csci6531-team2-joggle/assets/logs/" + g.getOwner().getUserName() + "-" + g.getSeed() + ".txt";
		//String logFile = "/Users/kneugent/GWU/CS6531/gwu-csci6531-team2-joggle/assets/logs/" + g.getOwner().getUserName() + "-" + g.getSeed() + ".txt";
		//String logFile = (ClassLoader.getSystemResource("/assets/logs/")).toString() + g.getOwner().getUserName() + "-" + g.getSeed() + ".txt";
		String result = "";
		int numBadWordlists = 0;
		int numStatLikely = 0;
		ArrayList<String> playerNames = g.getPlayers();
		ArrayList<String> wordLists = g.getWordLists();
		
		// count missing word lists as bad word lists
		numBadWordlists += playerNames.size() - wordLists.size();
		
		// write game log file
		log += "Game log:\n";
		log += g.toString() + "\n";

		for(String list : wordLists) {
			// check each player's word list against dictionary
			String[] wList = list.split("#");
			String words = "";
			
			ArrayList<String> checkList = new ArrayList<String>(wList.length-1);
			// index 0 is Player's name
			for(int i = 1; i < wList.length; i++) {
				checkList.add(wList[i]);
				words += wList[i] + "\n";
			}
			ArrayList<String> validWords = g.getDictionary().binarySearch(checkList);
			
			// check each player's word list for invalid words, which would indicate cheating
			if(((wList.length - 1) - validWords.size()) > 0) {
				numBadWordlists++;
				log += "Player " + wList[0] + " submitted a bad word list:\n" + words;
				log += "Player " + wList[0] + " should be awarded 0 points for the game.";
			}
			// check statistical likelihood of each player's word list size
			// assumed value for <= 3-sigma abnormal
			else if(wList.length - 1 > 50) {
				log += "Player " + wList[0] + " submitted a statistically unlikely word list:\n" + words;
				log += "Player " + wList[0] + " should be awarded 0 points for the game.\n";
			}
			else {
				// award points to player for each valid word
				// uses scoring method explained at http://en.wikipedia.org/wiki/Boggle#Rules
				numStatLikely++;
				int points = 0;
				for(String word : validWords) {
          Board gameBoard = g.getBoard();
          if(gameBoard.isWordThere(word)) {
                 if(word.length() == 3 || word.length() == 4) { points += 1; }
                 else if(word.length() == 5) { points += 2; }
                 else if(word.length() == 6) { points += 3; }
                 else if(word.length() == 7) { points += 5; }
                 else if(word.length() >= 8) { points += 11; }
          }
				}

				log += "Player " + wList[0] + " submitted a valid word list:\n" + words;
				log += "Player " + wList[0] + " should be awarded " + points + " points for the game.\n";
				Player p = getPlayerFromName(wList[0]);
				p.setPoints(p.getPoints() + points);
				log += "Awarded " + points + " to " + p.getUserName() + ", giving them a total of " + getPlayerPoints(p) + " points.\n";
			}
		}
		
		// require only 1 suspicious thing during the game to trigger an audit
		System.out.println("Number of complaints: " + g.getNumComplaints());
		if(g.getNumComplaints() >= 1 || numBadWordlists >= 1 || numStatLikely < players.size()) {
			if(auditors.size() > 0) {
				Auditor a = auditors.get(rng.nextInt(auditors.size()));
				a.addAuthorizedGame(g);
				log += "\nTHIS GAME HAS BEEN ASSIGNED TO " + a.getUserName() + " FOR AN AUDIT!\n";
				result = "INVALID GAME RESULTS! ";
			} else {
				System.out.println("No auditors were available to receive Game " + g.getSeed());
			}
		} else {
			result += "VALID GAME RESULTS!\n";
		}

		log += "There were " + g.getNumComplaints() + " complaints.\n";
		log += "There were " + numBadWordlists + " bad word lists submitted.\n";
		log += "There were " + (playerNames.size() - numStatLikely) + " statistically unlikely, suspiciously long word lists submitted.\n";
		log += "End game log.\n";
		
		// write log to disk
		File newLogFile = new File(logFile);
		FileWriter out;
		try {
			out = new FileWriter(newLogFile);
			out.write(log);
			out.close();
			result += "Wrote log to " + logFile + "\n";
		} catch (IOException e) {
			e.printStackTrace();
			result += "Failed to write log file to disk.\n";
		} finally {
			return result;
		}
	}
	
	/**
	 * @param msg Send a basic message to an authorized Client
	 * @param c Client
	 * @deprecated TESTING: This method should be replaced by network/socket communication
	 */
	protected void sendClientMessage(String msg, Client c) {
		if(authedClients.contains(c)) {
			c.receiveServerMessage(msg);
		} else {
			System.out.println("Spamming is bad.");
		}
	}

	/**
	 * @param msg Invoked by Client to send this Server a message
	 * @param c Client
	 * @deprecated TESTING: This method should be replaced by network/socket communication
	 */
	protected void receiveClientMessage(String msg, Client c) {
		if(!msg.equals("kthx")) {
			if(authedClients.contains(c)) {
				System.out.println();
				System.out.println("Client " + c.getID() + " said:");
				System.out.println(msg);
				System.out.println();
				sendClientMessage("kthx", c);
			}
		}
	}

	/**
	 * Filters out potentially malicious input based on its type.  Guarantees safety, not validity of input (i.e. doesn't tell you whether email is of the form someone@website.com).
	 * @param input The input to be filtered
	 * @param type The type of regular expression filter to apply to the input.  Valid types are "email", "sql", "word", and "profile".  Types "email," "word," and "profile" should be used to sanitize user input before its combined with SQL commands.  Type "sql" should be used after other types are combied with SQL commands.
	 * @return String sanitized input
	*/
	protected static String sanitizeInput(String input, String type) {
		String t = type;
		String cleanOutput = "";

		// failsafe default if type isn't specified correctly
		if(!(type.equals("email") || type.equals("sql") || type.equals("word") || type.equals("profile"))) {
			t = "word";
		}

		if(t.equals("email")) {
			// regex to filter everything but letters, numbers, at sign, period, and hyphen
			cleanOutput = input.replaceAll("[^\\.a-zA-Z0-9@-]*", "");			
		} else if(t.equals("sql")) {
			// regex to filter out bad SQL
			cleanOutput = input.replaceAll("[^a-zA-Z0-9!@*\\(\\)'\"-=; ]*", "");
		} else if(t.equals("word")) {
			// regex to filter everything but letters
			cleanOutput = input.replaceAll("[^a-zA-Z]*", "");
		} else if(t.equals("profile")) {
			// regex to replace one or more white space (tab, new line, etc.) with a single space
			String temp = input.replace("[\t|\n|\r]*", " ");
			// regex to filter everything but letters, numbers, and common punctuation
			cleanOutput = temp.replaceAll("[^\\.a-zA-Z0-9-,!? ]*", "");
		}

		return cleanOutput;
	}

	/**
	 * @return boolean  true if the given email address is of the proper form
	 * @param email The email address to validate
	 */
	// returns true if the email is of the proper form
	protected static boolean isValidEmail(String email) {
		return email.matches("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,4}$") || email.matches("^[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,4}$");
	}

    /**
     * helper method
     * @param tableName String
     * @param fields String[]
     * @param types String[]
     * @param primaryKey String
     * @return String
     */
	protected String createTable(String tableName, String[] fields, String[] types, String primaryKey) throws SQLiteException {
		ArrayList<String> tableFields = new ArrayList<String>(fields.length);
		for(String f : fields) { tableFields.add(f); }
		ArrayList<String> tableTypes = new ArrayList<String>(types.length);
		for(String t : types) { tableTypes.add(t); }
		return createTable(tableName, tableFields, tableTypes, primaryKey);
	}

	/**
	 * Creates a new database table with the given name, fields, datatypes, and primary key.  Returns name of newly created table on success or "Error" on failure.
	 * @param tableName The name of the new table to create
	 * @param fields An ArrayList wherein each item is the name of a column in the new table
	 * @param types An ArrayList wherein each item specifies the datatype of the corresponding field
	 * @param String primaryKey The field to designate as the primary key for the new table
	 * @return String
	 */
	private String createTable(String tableName, ArrayList<String> fields, ArrayList<String> types, String primaryKey) throws SQLiteException {
		if(tableName.equals("") || fields.size() == 0 || types.size() == 0 || primaryKey.equals("")) {
			throw new SQLiteException(0, "There is an error in your syntax. All fields are required. Check that your strings and ArrayLists aren't empty.");
		}

		if(fields.size() != types.size()) {
			throw new SQLiteException(0, "There is an error in your syntax. Check that every table field specifies a data type.");
		}

		// sanitize individual inputs
		final String cleanTable = sanitizeInput(tableName, "word");
		final ArrayList<String> cleanFields = new ArrayList<String>(fields.size());
		for(String f : fields) { cleanFields.add(sanitizeInput(f, "word")); }
		final ArrayList<String> cleanTypes = new ArrayList<String>(types.size());
		for(String t : types) { cleanTypes.add(sanitizeInput(t, "word")); }
		final String cleanPrimaryK = sanitizeInput(primaryKey, "word");

		// create sanitized command from cleaned inputs
		String cmd = "CREATE TABLE " + cleanTable + "(";
		for(int i = 0; i < cleanFields.size(); i++) {
			cmd += cleanFields.get(i) + " " + cleanTypes.get(i);
			if(cleanFields.get(i).equals(cleanPrimaryK)) {
				cmd += " PRIMARY KEY";
			}
			if(i < cleanFields.size()-1) {
				cmd += ", ";
			}
		}
		cmd += ");";
		//System.out.println(cmd);
		final String cleanCmd = sanitizeInput(cmd, "sql");
		//System.out.println(cleanCmd);

		return dbQueue.execute(new SQLiteJob<String>() {
			protected String job(SQLiteConnection connection) throws SQLiteException {
				SQLiteStatement st = connection.prepare(cleanCmd);
				String status = "";
				try {
					st.step();
					if(st.hasStepped() || st.hasRow()) {
						status = "Successfully created table ";
						//status += st.getColumnTableName(0);
					} else {
						status = "Unable to execute SQL statement. 1";
					}
					return status;
				} catch (Exception e) {
					//e.printStackTrace();
					return status;
				} finally {
					st.dispose();
				}
			}
		}).complete();
	}

    /**
     * helper method
     * @param tableName String
     * @param fields String[]
     * @param values String[]
     * @return String
     */
	private String insertRow(String tableName, String[] fields, String[] values) {
		ArrayList<String> tableFields = new ArrayList<String>(fields.length);
		for(String f : fields) { tableFields.add(f); }
		ArrayList<String> tableValues = new ArrayList<String>(values.length);
		for(String v : values) { tableValues.add(v); }
		return insertRow(tableName, tableFields, tableValues);
	}

    /**
     * inserts a new row
     * returns name of newly created table on success or "Error" on failure
     * @param tableName String
     * @param fields ArrayList<String>
     * @param values ArrayList<String>
     * @return String
     */
	private String insertRow(String tableName, ArrayList<String> fields, ArrayList<String> values) {

		// sanitize individual inputs
		final String cleanTable = sanitizeInput(tableName, "word");
		final ArrayList<String> cleanFields = new ArrayList<String>(fields.size());
		for(String f : fields) { cleanFields.add(sanitizeInput(f, "word")); }
		final ArrayList<String> cleanValues = new ArrayList<String>(values.size());
		for(String v : values) { cleanValues.add(sanitizeInput(v, "sql")); }

		// create sanitized command from cleaned inputs
		String cmd = "INSERT INTO " + cleanTable + " (";
		for(int i = 0; i < cleanFields.size(); i++) {
			cmd += cleanFields.get(i);
			if(i < cleanFields.size()-1) {
				cmd += ", ";
			} else {
				cmd += ") VALUES (";
			}
		}
		for(int i = 0; i < cleanValues.size(); i++) {
			cmd += "\"" + cleanValues.get(i) + "\"";
			if(i < cleanValues.size()-1) {
				cmd += ", ";
			} else {
				cmd += ");";
			}
		}
		//System.out.println(cmd);
		final String cleanCmd = sanitizeInput(cmd, "sql");
		//System.out.println(cleanCmd);

		return dbQueue.execute(new SQLiteJob<String>() {
			protected String job(SQLiteConnection connection) throws SQLiteException {
				SQLiteStatement st = connection.prepare(cleanCmd);
				String status = "";
				try {
					st.step();
					if(st.hasStepped() || st.hasRow()) {
						//status = st.getColumnTableName((int) connection.getLastInsertId());
						//status = st.getColumnName(0);
						status = "Successfully insterted data into table ";
					} else {
						status = "Unable to execute SQL statement. 2";
					}
					st.dispose();
				} catch (Exception e) {
					//e.printStackTrace();
				} finally {
					return status;
				}
			}
		}).complete();
	}
	
    /**
     * get the total number of entries in a table
     * returns -1 on error
     * @param tableName String
     * @return int
     */
	private int getTableRowCount(String tableName) {
		final String cleanTable = sanitizeInput(tableName, "word");
		return dbQueue.execute(new SQLiteJob<Integer>() {
			protected Integer job(SQLiteConnection connection) throws SQLiteException {
				SQLiteStatement st = connection.prepare(sanitizeInput("SELECT COUNT(*) FROM " + cleanTable + ";", "sql"));
				try {
					st.step();
					return st.columnInt(0);
				} catch (Exception e) {
					//System.out.println("Unable to execute SQL statement. 3");
					//e.printStackTrace();
					return -1;
				} finally {
					st.dispose();
				}
			}
		}).complete();
	}

    /**
     * helper method
     * @param tableName String
     * @param fields String[]
     * @param values String[]
     * @return int
     * @throws SQLiteException
     */
	private int getTableRowCount(String tableName, String[] fields, String[] values) throws SQLiteException {
		ArrayList<String> tableFields = new ArrayList<String>(fields.length);
		for(String f : fields) { tableFields.add(f); }
		ArrayList<String> tableValues = new ArrayList<String>(values.length);
		for(String v : values) { tableValues.add(v); }
		return getTableRowCount(tableName, tableFields, tableValues);
	}	

    /**
     * get the number of entries in a table where the given fields match the given values
     * returns -1 on error
     * @param tableName String
     * @param fields ArrayList<String>
     * @param values ArrayList<String>
     * @return int
     */
	private int getTableRowCount(String tableName, ArrayList<String> fields, ArrayList<String> values) {

		// sanitize individual inputs
		final String cleanTable = sanitizeInput(tableName, "word");
		final ArrayList<String> cleanFields = new ArrayList<String>(fields.size());
		for(String f : fields) { cleanFields.add(sanitizeInput(f, "word")); }
		final ArrayList<String> cleanValues = new ArrayList<String>(values.size());
		for(String v : values) { cleanValues.add(sanitizeInput(v, "sql")); }

		// create sanitized command from cleaned inputs
		String cmd = "SELECT COUNT(*) FROM " + cleanTable + " WHERE ";
		for(int i = 0; i < cleanFields.size(); i++) {
			cmd += cleanFields.get(i) + "=\"" + cleanValues.get(i) + "\"";
			if(i < cleanFields.size()-1) {
				cmd += " AND ";
			}
		}
		cmd += ";";
		//System.out.println(cmd);
		final String cleanCmd = sanitizeInput(cmd, "sql");
		System.out.println(cleanCmd);

		return dbQueue.execute(new SQLiteJob<Integer>() {
			protected Integer job(SQLiteConnection connection) throws SQLiteException {
				SQLiteStatement st = connection.prepare(cleanCmd);
				try {
					st.step();
					return st.columnInt(0);
				} catch (Exception e) {
					//System.out.println("Unable to execute SQL statement. 4");
					//e.printStackTrace();
					return -1;
				} finally {
					st.dispose();
				}
			}
		}).complete();
	}
	
    /**
     * helper method
     * @param tableName String
     * @param sFields String[]
     * @returns ArrayList<String>
     */
	private ArrayList<String> getCellValues(String tableName, String[] sFields) {
		ArrayList<String> selectFields = new ArrayList<String>(sFields.length);
		for(String sf : sFields) { selectFields.add(sf); }
		return getCellValues(tableName, selectFields);
	}
	
    /**
     * @param tableName String
     * @param selectFields ArrayList<String>
     * @return ArrayList<String>
     */
	private ArrayList<String> getCellValues(String tableName, ArrayList<String> selectFields) {
		// sanitize individual inputs
		final String cleanTable = sanitizeInput(tableName, "word");
		final ArrayList<String> cleanSelectFields = new ArrayList<String>(selectFields.size());
		for(String sf : selectFields) { cleanSelectFields.add(sanitizeInput(sf, "sql")); }	

		// create sanitized command from cleaned inputs
		String cmd = "SELECT ";
		for(int i = 0; i < cleanSelectFields.size(); i++) {
			cmd += cleanSelectFields.get(i);
			if(i < cleanSelectFields.size()-1) {
				cmd += ", ";
			} else {
				cmd += " FROM " + cleanTable + ";";
			}
		}
		//System.out.println(cmd);
		final String cleanCmd = sanitizeInput(cmd, "sql");
		//System.out.println(cleanCmd);

		return dbQueue.execute(new SQLiteJob<ArrayList<String>>() {
			protected ArrayList<String> job(SQLiteConnection connection) throws SQLiteException {
				ArrayList<String> resultSet = new ArrayList<String>();
				SQLiteStatement st = connection.prepare(cleanCmd);
				try {
					while(st.step()) {
						resultSet.add(st.columnString(0));	
					}
					return resultSet;
				} catch (Exception e) {
					//System.out.println("Unable to execute SQL statement. 5");
					//e.printStackTrace();
					return null;
				} finally {
					st.dispose();
				}
				
			}
		}).complete();
	}
	
    /**
     * helper method
     * @param tableName String
     * @param sFields String[]
     * @param wFields String[]
     * @param wValues String[]
     * @return ArrayList<String>
     */
	private ArrayList<String> getCellValues(String tableName, String[] sFields, String[] wFields, String[] wValues) {
		ArrayList<String> selectFields = new ArrayList<String>(sFields.length);
		for(String sf : sFields) { selectFields.add(sf); }
		ArrayList<String> whereFields = new ArrayList<String>(wFields.length);
		for(String wf : wFields) { whereFields.add(wf); }
		ArrayList<String> whereValues = new ArrayList<String>(wValues.length);
		for(String wv : wValues) { whereValues.add(wv); }
		return getCellValues(tableName, selectFields, whereFields, whereValues);
	}
	
    /**
     * helper method
     * @param tableName String
     * @param selectFields ArrayList<String>
     * @param whereFields ArrayList<String>
     * @param whereValues ArrayList<String>
     * @return ArrayList<String>
     */
	private ArrayList<String> getCellValues(String tableName, ArrayList<String> selectFields, ArrayList<String> whereFields, ArrayList<String> whereValues) {
		// sanitize individual inputs
		final String cleanTable = sanitizeInput(tableName, "word");
		final ArrayList<String> cleanSelectFields = new ArrayList<String>(selectFields.size());
		for(String sf : selectFields) { cleanSelectFields.add(sanitizeInput(sf, "sql")); }		
		final ArrayList<String> cleanWhereFields = new ArrayList<String>(whereFields.size());
		for(String wf : whereFields) { cleanWhereFields.add(sanitizeInput(wf, "word")); }
		final ArrayList<String> cleanWhereValues = new ArrayList<String>(whereValues.size());
		for(String wv : whereValues) { cleanWhereValues.add(sanitizeInput(wv, "sql")); }

		// create sanitized command from cleaned inputs
		String cmd = "SELECT ";
		for(int i = 0; i < cleanSelectFields.size(); i++) {
			cmd += cleanSelectFields.get(i);
			if(i < cleanSelectFields.size()-1) {
				cmd += ", ";
			} else {
				cmd += " FROM " + cleanTable + " WHERE ";
			}
		}		
		for(int i = 0; i < cleanWhereFields.size(); i++) {
			cmd += cleanWhereFields.get(i) + "=\"" + cleanWhereValues.get(i) + "\"";
			if(i < cleanWhereFields.size()-1) {
				cmd += " AND ";
			}
		}
		cmd += ";";
		//System.out.println(cmd);
		final String cleanCmd = sanitizeInput(cmd, "sql");
		//System.out.println(cleanCmd);

		return dbQueue.execute(new SQLiteJob<ArrayList<String>>() {
			protected ArrayList<String> job(SQLiteConnection connection) throws SQLiteException {
				ArrayList<String> resultSet = new ArrayList<String>();
				SQLiteStatement st = connection.prepare(cleanCmd);
				try {
					
					st.step();
					for(int i = 0; i < st.columnCount(); i++) {
						resultSet.add(st.columnString(i));
					}
					return resultSet;
				} catch (Exception e) {
					System.out.println("User's role is invalid.");
					//e.printStackTrace();
					return resultSet;
				} finally {
					st.dispose();
				}
			}
		}).complete();			
	}
	
    /**
     * helper method
     * @param tableName String
     * @param uFields String[]
     * @param uValues String[]
     * @param wFields String[]
     * @param wValues String[]
     * @return ArrayList<String>
     * @throws SQLiteException
     */
	private ArrayList<String> setCellValues(String tableName, String[] uFields, String[] uValues, String[] wFields, String[] wValues) throws SQLiteException {
		ArrayList<String> updateFields = new ArrayList<String>(uFields.length);
		for(String uf : uFields) { updateFields.add(uf); }
		ArrayList<String> updateValues = new ArrayList<String>(uValues.length);
		for(String uv : uValues) { updateValues.add(uv); }
		ArrayList<String> whereFields = new ArrayList<String>(wFields.length);
		for(String wf : wFields) { whereFields.add(wf); }
		ArrayList<String> whereValues = new ArrayList<String>(wValues.length);
		for(String wv : wValues) { whereValues.add(wv); }
		return setCellValues(tableName, updateFields, updateValues, whereFields, whereValues);
	}
	
    /**
     * @param tableName String
     * @param updateFields ArrayList<String>
     * @param updateValues ArrayList<String>
     * @param whereFields ArrayList<String>
     * @param whereValues ArrayList<String>
     * @return ArrayList<String>
     */
	private ArrayList<String> setCellValues(String tableName, ArrayList<String> updateFields, ArrayList<String> updateValues, ArrayList<String> whereFields, ArrayList<String> whereValues) {
		if((updateFields.size() != updateValues.size()) || (whereFields.size() != whereValues.size())) {
			//throw new SQLiteException(0, "There is an error in your syntax. Check that every table field has a value.");
			System.out.println("There is an error in your syntax. Check that every table field has a value.");
			return new ArrayList<String>();
		} else {
		
			// sanitize individual inputs
			final String cleanTable = sanitizeInput(tableName, "word");
			final ArrayList<String> cleanUpdateFields = new ArrayList<String>(updateFields.size());
			for(String uf : updateFields) { cleanUpdateFields.add(sanitizeInput(uf, "word")); }
			final ArrayList<String> cleanUpdateValues = new ArrayList<String>(updateValues.size());
			for(String uv : updateValues) { cleanUpdateValues.add(sanitizeInput(uv, "sql")); }	
			final ArrayList<String> cleanWhereFields = new ArrayList<String>(whereFields.size());
			for(String wf : whereFields) { cleanWhereFields.add(sanitizeInput(wf, "word")); }
			final ArrayList<String> cleanWhereValues = new ArrayList<String>(whereValues.size());
			for(String wv : whereValues) { cleanWhereValues.add(sanitizeInput(wv, "sql")); }
	
			// create sanitized command from cleaned inputs
			String cmd = "UPDATE " + cleanTable + " SET ";
			for(int i = 0; i < cleanUpdateFields.size(); i++) {
				cmd += cleanUpdateFields.get(i) + "=\"" + cleanUpdateValues.get(i) + "\"";
				if(i < cleanUpdateFields.size()-1) {
					cmd += ", ";
				} else {
					cmd += " WHERE ";
				}
			}		
			for(int i = 0; i < cleanWhereFields.size(); i++) {
				cmd += cleanWhereFields.get(i) + "=\"" + cleanWhereValues.get(i) + "\"";
				if(i < cleanWhereFields.size()-1) {
					cmd += " AND ";
				}
			}
			cmd += ";";
			//System.out.println(cmd);
			final String cleanCmd = sanitizeInput(cmd, "sql");
			//System.out.println(cleanCmd);
	
			return dbQueue.execute(new SQLiteJob<ArrayList<String>>() {
				protected ArrayList<String> job(SQLiteConnection connection) throws SQLiteException {
					ArrayList<String> resultSet = new ArrayList<String>();
					SQLiteStatement st = connection.prepare(cleanCmd);
					try {
						st.step();
						for(int i = 0; i < st.columnCount(); i++) {
							resultSet.add(st.columnString(i));
						}
						return resultSet;
					} catch (Exception e) {
						//System.out.println("Unable to execute SQL statement. 7");
						//e.printStackTrace();
						return resultSet;
					} finally {
						st.dispose();
					}
				}
			}).complete();
		}
	}

    /** 
     * delete a table row by ID number
     * @param tableName String
     * @param id int
     * @return String
     */
	private String deleteRowID(String tableName, final int id) {
		final String cleanTable= sanitizeInput(tableName, "word");
		return dbQueue.execute(new SQLiteJob<String>() {
			protected String job(SQLiteConnection connection) throws SQLiteException {
				SQLiteStatement st = connection.prepare(sanitizeInput("DELETE FROM " + cleanTable + " WHERE id='" + id + "';", "sql"));
				try {
					st.step();
					return "Deleted " + connection.getChanges() + "rows from " + cleanTable + ".";
				} catch (Exception e) {
					//System.out.println("Unable to execute SQL statement. 8");
					//e.printStackTrace();
					return "Error";
				} finally {
					st.dispose();
				}
			}
		}).complete();
	}

    /**
     * delete a table row by ID name
     * @param tableName String
     * @param id String
     * @return String
     */
	private String deleteRowID(String tableName, final String id) {
		final String cleanTable= sanitizeInput(tableName, "word");
		return dbQueue.execute(new SQLiteJob<String>() {
			protected String job(SQLiteConnection connection) throws SQLiteException {
				SQLiteStatement st = connection.prepare(sanitizeInput("DELETE FROM " + cleanTable + " WHERE id='" + id + "';", "sql"));
				try {
					st.step();
					return "Deleted " + connection.getChanges() + "rows from " + cleanTable + ".";
				} catch (Exception e) {
					//System.out.println("Unable to execute SQL statement. 9");
					//e.printStackTrace();
					return "Error";
				} finally {
					st.dispose();
				}
			}
		}).complete();
	}

	
    /**
     * helper method
     * @param tableName String
     * @param fields String[]
     * @param values String[]
     * @return String
     * @throws SQLiteException
     */
	private String deleteRow(String tableName, String[] fields, String[] values) throws SQLiteException {
		ArrayList<String> tableFields = new ArrayList<String>(fields.length);
		for(String f : fields) { tableFields.add(f); }
		ArrayList<String> tableValues = new ArrayList<String>(values.length);
		for(String v : values) { tableValues.add(v); }
		return deleteRow(tableName, tableFields, tableValues);
	}	

    /**
     * delete a table row by finding fields with matching values
     * @param tableName String
     * @param fields ArrayList<String>
     * @param values ArrayList<String>
     * @return String
     */
	private String deleteRow(String tableName, ArrayList<String> fields, ArrayList<String> values) {

		// sanitize individual inputs
		final String cleanTable = sanitizeInput(tableName, "word");
		final ArrayList<String> cleanFields = new ArrayList<String>(fields.size());
		for(String f : fields) { cleanFields.add(sanitizeInput(f, "word")); }
		final ArrayList<String> cleanValues = new ArrayList<String>(values.size());
		for(String v : values) { cleanValues.add(sanitizeInput(v, "sql")); }

		// create sanitized command from cleaned inputs
		String cmd = "DELETE FROM " + cleanTable + " WHERE ";
		for(int i = 0; i < cleanFields.size(); i++) {
			cmd += cleanFields.get(i) + "='" + cleanValues.get(i) + "'";
			if(i < cleanFields.size()-1) {
				cmd += ", ";
			} else {
				cmd += ";";
			}
		}
		//System.out.println(cmd);
		final String cleanCmd = sanitizeInput(cmd, "sql");
		//System.out.println(cleanCmd);

		return dbQueue.execute(new SQLiteJob<String>() {
			protected String job(SQLiteConnection connection) throws SQLiteException {
				SQLiteStatement st = connection.prepare(cleanCmd);
				try {
					st.step();
					return "Deleted " + connection.getChanges() + "rows from " + cleanTable + ".";
				} catch (Exception e) {
					//System.out.println("Unable to execute SQL statement. 10");
					//e.printStackTrace();
					return "Error";
				} finally {
					st.dispose();
				}
			}
		}).complete();
	}

    /**
     * @param m String
     * @return String
     */
	protected String hash(String m) {
		byte[] msgBytes = m.getBytes();
		byte[] hash = sha.digest(msgBytes);
		return Arrays.toString(hash).replaceAll("[\\[\\]]", "");
	}
	
    /**
     * @param u User
     * @param pwd String
     * @return Boolean
     */
	protected Boolean validPassword(User u, String pwd) {
	    writeUserLogEntry("attempted a login\n", u);
		return (hash(pwd).equals(getUserPassword(u, admin)));
	}
	
	/**
	 * @param a This Server's Administrator (owner)
	 * @param maxC The maximum number of Clients this Server can safely handle
	 * Creates a new Server instance with a new dbConnection
	 * @throws SQLiteException 
	 */
	public Server(Administrator a, int maxC, String dictPath) throws SQLiteException {
		admin = a;
		maxClients = maxC;
		players = new ArrayList<Player>();
		authedClients = new ArrayList<Client>();
		auditors = new ArrayList<Auditor>();
		admins = new ArrayList<Administrator>();
		activeGames = new ArrayList<Game>(maxClients);
		availableDictionaries = new ArrayList<Dictionary>();
    currentlyCollecting = "";
		dictFolderPath = dictPath;
		File dbFile = new File("joggle-db");
		//File newDBFile = new File("joggle-db-" + Math.abs(rng.nextInt()));

		try {
			dbConnection = new SQLiteConnection(dbFile);
			dbConnection.open(true);
			dbQueue = new SQLiteQueue(dbFile);
			dbQueue.start();
		} catch (Exception e) {
			e.printStackTrace();
		}

		// create players table
		String[] playerFields = {"userName", "password", "publicKey", "firstName", "lastName", "email", "points", "gamesPlayed", "friends", "privacy", "inGame"};
		String[] playerTypes = {"TEXT", "TEXT", "TEXT", "TEXT", "TEXT", "TEXT", "INTEGER", "TEXT", "TEXT", "INTEGER", "BOOLEAN"};
		try {
			createTable(PLAYERS, playerFields, playerTypes, "userName");
		} catch (SQLiteException e) {
			//e.printStackTrace();
			System.out.println("Table " + PLAYERS + " exists...");
		}

		// create clients table
		String[] clientFields = {"id", "lastLogin", "ipAddress"};
		String[] clientTypes = {"INTEGER", "DATETIME", "TEXT"};
		try {
			createTable(CLIENTS, clientFields, clientTypes, "id");
		} catch (SQLiteException e) {
			//e.printStackTrace();
			System.out.println("Table " + CLIENTS + " exists...");
		}

		// create auditors table
		String[] auditorFields = {"userName", "password", "publicKey", "firstName", "lastName", "email", "authorizedGames", "authorizedPlayers"};
		String[] auditorTypes = {"TEXT", "TEXT", "TEXT", "TEXT", "TEXT", "TEXT", "TEXT", "TEXT"};
		try {
			createTable(AUDITORS, auditorFields, auditorTypes, "userName");
		} catch (SQLiteException e) {
			//e.printStackTrace();
			System.out.println("Table " + AUDITORS + " exists...");
		}

		// create administrators table
		String[] adminFields = {"userName", "password", "publicKey", "firstName", "lastName", "email"};
		String[] adminTypes = {"TEXT", "TEXT", "TEXT", "TEXT", "TEXT", "TEXT"};
		try {
			createTable(ADMINS, adminFields, adminTypes, "userName");
		} catch (SQLiteException e) {
			//e.printStackTrace();
			System.out.println("Table " + ADMINS + " exists...");
		}
		addAdmin(a);
		
		// create active games table
		String[] activeGameFields = {"id", "minPlayers", "maxPlayers", "minPoints", "maxPoints", "maxWait", "duration", "seed", "size", "owner", "players", "allowedPlayers", "bannedPlayers", "dictionary"};
		String[] activeGameTypes = {"TEXT", "INTEGER", "INTEGER", "INTEGER", "INTEGER", "INTEGER", "INTEGER", "INTEGER", "INTEGER", "TEXT", "TEXT", "TEXT", "TEXT", "TEXT"};
		try {
			createTable(ACTIVE_GAMES, activeGameFields, activeGameTypes, "id");
		} catch (SQLiteException e) {
			//e.printStackTrace();
			System.out.println("Table " + ACTIVE_GAMES + " exists...");
		}

		// create historic games table
		String[] historicGameFields = {"id", "minPlayers", "maxPlayers", "minPoints", "maxPoints", "maxWait", "duration", "seed", "size", "owner", "players", "allowedPlayers", "bannedPlayers", "dictionary"};
		String[] historicGameTypes = {"TEXT", "INTEGER", "INTEGER", "INTEGER", "INTEGER", "INTEGER", "INTEGER", "INTEGER", "INTEGER", "TEXT", "TEXT", "TEXT", "TEXT", "TEXT"};
		try {
			createTable(HISTORIC_GAMES, historicGameFields, historicGameTypes, "id");
		} catch (SQLiteException e) {
			//e.printStackTrace();
			System.out.println("Table " + HISTORIC_GAMES + " exists...");
		}
		
		init();
	}

	/**
	 * @param a This Server's Administrator (owner)
	 * @param maxC The maximum number of Clients this Server can safely handle
	 * @param dictPath String
	 * @param dbFile The full path to a pre-existing SQLite dbConnection file
	 * Creates a new Server instance with an existing dbConnection
	 */
	public Server(Administrator a, int maxC, String dictPath, String dbFile) {
		admin = a;
		maxClients = maxC;
		players = new ArrayList<Player>();
		authedClients = new ArrayList<Client>();
		auditors = new ArrayList<Auditor>();
		admins = new ArrayList<Administrator>();
		activeGames = new ArrayList<Game>(maxClients);
		availableDictionaries = new ArrayList<Dictionary>();
     currentlyCollecting = "";
		dictFolderPath = dictPath;
		File existingDBFile = new File(dbFile);

		try {
			dbConnection = new SQLiteConnection(existingDBFile);
			dbConnection.open(true);
			dbQueue = new SQLiteQueue(existingDBFile);
			dbQueue.start();
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		init();
	}

	// make sure the database is setup
	public void init() {
		// for each Dictionary file in dictFolderPath, add to availableDictionaries
		File dictDir = new File(dictFolderPath);
		if(dictDir.isDirectory() && dictDir.length() > 0) {
			for(File f : dictDir.listFiles()) {
				availableDictionaries.add(new Dictionary(this, f.getName()));
			}
			System.out.println("Server started! " + availableDictionaries.size() + " dictionaries are available in " + dictFolderPath + ".");
			
			// make sure the SQLite server is running
			try {
				System.out.println("The Joggle database is powered by SQLite4Java " + SQLite.getSQLiteVersion() + " using SQLite library version " + SQLite.getLibraryVersion() + ". The database is currently using " + SQLite.getMemoryUsed() / 1024 + " KB of memory.");
			} catch (Exception e) {
				e.printStackTrace();
			}
			
			// set up crypto
			try {
				sha = MessageDigest.getInstance("SHA-512");
			} catch (NoSuchAlgorithmException nsae) {
				nsae.printStackTrace();
			}
			
		} else {
			System.out.println("No dictionary found!");
		}
		
		System.out.println("Contact " + admin.getFirstName() + " " + admin.getLastName() + " at " + admin.getEmail() + " to report a bug.");
	}

    /**
     * Call a function to update teh passed user's log file with the action taken on behalf of that user
     * @param action String
     * @param user User
     */
    private void writeUserLogEntry (String action, User user) {
	user.updateUserLog(action);
    }

	public static void main(String[] args) throws SQLiteException
	{
		Administrator serverAdmin = new Administrator("Super", "User", "superman", "admin@gmail.com", "password123");
		//Server server = new Server(serverAdmin, 256, "/Users/steve/workspace/gwu-csci6531-team2-joggle/assets/dictionaries/", "joggle-db");
		Server server = new Server(serverAdmin, 256, (ClassLoader.getSystemResource("assets/dictionaries/")).toString(), "joggle-db");
		
        try {
            SSLServerSocketFactory sslserversocketfactory =
                    (SSLServerSocketFactory) SSLServerSocketFactory.getDefault();
            SSLServerSocket sslserversocket =
                    (SSLServerSocket) sslserversocketfactory.createServerSocket(7066);
            while(true)
            {
                SSLSocket sslsocket = (SSLSocket) sslserversocket.accept();

                InputStream inputstream = sslsocket.getInputStream();
                InputStreamReader inputstreamreader = new InputStreamReader(inputstream);
                BufferedReader bufferedreader = new BufferedReader(inputstreamreader);

                String string = null;
                while ((string = bufferedreader.readLine()) != null) {
                    System.out.println(string);
                    System.out.flush();
                }
                server.receiveClientMessage(string, new Client(server));
            }
        } catch (Exception exception) {
            exception.printStackTrace();
        }

	}
}

